import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Scanner;

import javax.swing.Timer;

/**
 * handles interrupts and low level OS tasks.
 * @author Patrick Black
 */
public class CPU {
	// 2 second delay on system timer.
	private static final int DELAY = 2000;
	// address of next instruction.
	private int pc = 0;
	
	// System Timer
	private Timer myTimer;
	// Scheduler
	private Scheduler scheduler;
	// Current Process
	private Process currentProcess;
	//shared memory
	private int[] sharedMemory = new int[10];
	

	/**
	 * creates a system timer and assigns the first process from the scheduler.
	 * initializes shared memory.
	 */
	public CPU(int algorithm) {
		scheduler = new Scheduler();
		scheduler.setAlgorithm(algorithm);
		currentProcess = scheduler.nextProcess();
		
		for(int i = 0; i < 10; i++) {
			sharedMemory[i] = 0;
		}

		
		myTimer = new Timer(DELAY, new ActionListener() {
		
		@Override
		public void actionPerformed(ActionEvent arg0) {
			timerInterrupt();
		}
		});
		myTimer.setRepeats(false);
		myTimer.start();
			
	}
	
	
	/**
	 * assigns the first process.
	 * 
	 * @param proc first process
	 */
	protected void setCurrentProcess(Process proc) {
		currentProcess = proc;
	}
	
	/**
	 * 
	 * @param process process to unblock.
	 */
	protected void unblock(Process process) {
		scheduler.unBlock(process);
	}
	
	/**
	 * 
	 * @param proc the current process.
	 * @return the address of the current instruction.
	 */
	protected int pcInc(Process proc) {
		pc = (proc.fetchnxtStep()) % proc.fetchnoStep();
		currentProcess.setnxtStep(proc.fetchnxtStep()+1);
		
		// handles system calls
		if(proc.getSysCallInstruction(pc) == 1) {
			// for io devices
			if( (proc.getSystemCall().equals("IO") || proc.getSystemCall().equals("Aux"))) {
				
				sysCallInterrupt(proc);
				
				//consumer
			} else if(proc.getSystemCall().equals("Read")) {
				readMemory();
				//producer
			} else if(proc.getSystemCall().equals("Write")) {
				writeMemory();
			}
		}
		
		
		
		return pc;
	}
	
	/**
	 * interrupt triggered by the timer.
	 * sets state and current pc in the process and them sends them back to the scheduler.
	 * assigns new current process and sets pc.
	 */
	protected void timerInterrupt() {
		myTimer.stop();
		System.out.println("Device 0 (System Timer) produced an interrupt.");
		currentProcess.setnxtStep(pc);
		currentProcess.setState(Process.procState.READY);
		scheduler.addProcess(currentProcess);
		
		
		Process next = scheduler.nextProcess();
		
		
		setCurrentProcess(next);
		currentProcess.setState(Process.procState.RUNNING);
		
		pc = currentProcess.fetchnxtStep();
		myTimer.restart();
	}

	/**
	 * sets current process pc and state to blocked. sends back to the scheduler.
	 * assigns new current process and pc.
	 * 
	 * @param process process doing system call.
	 */
	protected void sysCallInterrupt(Process process) {
		
		System.out.println("Process " + process.fetchName() +
				" produced an interrupt.");
		process.setnxtStep(pc);
		process.setState(Process.procState.BLOCKED);
		
		scheduler.addProcess(process);
		
		
		if (currentProcess.getSystemCall().equals("IO")) {
			System.out.println("System Call to device IO\n");
			UI ui = new UI(this, currentProcess);
			ui.run();
		} else if (currentProcess.getSystemCall().equals("Aux")) {
			System.out.println("System Call to device AUX\n");
			OtherIO other = new OtherIO(this, process);
			other.run();
		}
		
		
		Process next = scheduler.nextProcess();
		
		
		setCurrentProcess(next);
		currentProcess.setState(Process.procState.RUNNING);
		
		pc = currentProcess.fetchnxtStep();
	}
	
	/**
	 * if int at memory location is 0 updates process info and sends back to scheduler.
	 * assigns new current process.
	 * 
	 * if not decrements int at memory location and unblocks producer.
	 */
	protected void readMemory() {
		
		//int location = 0 + (int)(Math.random()*sharedMemory.length);
		int location = 1;
		
		if(sharedMemory[location] == 0) {
			System.out.println("Process Consumer blocked until shared memory location 1 available");
			currentProcess.setnxtStep(pc);
			currentProcess.setState(Process.procState.BLOCKED);
			currentProcess.setWaitingOn(location);
			
			scheduler.addProcess(currentProcess);
			
			Process next = scheduler.nextProcess();
			
			setCurrentProcess(next);
			currentProcess.setState(Process.procState.RUNNING);
			
			pc = currentProcess.fetchnxtStep();
			
		} else {
			if(!scheduler.emptyBlocked()) {
				scheduler.unBlockConsumerProducer(location);
			}
			
			sharedMemory[location] = sharedMemory[location] - 1;
			
		}
	}
	
	/**
	 * if int at memory location is 0 unblocks consumer and increments int.
	 * 
	 * if not updates process info and sends back to scheduler.
	 * assigns a new current process and pc.
	 */
	protected void writeMemory() {
		
		//int location = 0 + (int)(Math.random()*sharedMemory.length);
		int location = 1;
		
		if(sharedMemory[location] == 0) {
			if(!scheduler.emptyBlocked()) {
				scheduler.unBlockConsumerProducer(location);
			}
			
			
		} else {
			/*currentProcess.setnxtStep(pc);
			currentProcess.setState(Process.procState.BLOCKED);
			currentProcess.setWaitingOn(location);
			scheduler.addProcess(currentProcess);
			
			Process next = scheduler.nextProcess();
			
			setCurrentProcess(next);
			currentProcess.setState(Process.procState.RUNNING);
			System.out.println("Scheduler: Process " + currentProcess.fetchName() + " running.");
			pc = currentProcess.fetchnxtStep();
			myTimer.restart();*/
		}
		sharedMemory[location] = sharedMemory[location] + 1;
		
	}
	
	/**
	 * calls pcInc to advance pc.
	 */
	public void runProcess() {
		pcInc(currentProcess);
	}

}
